package com.kay;

import com.kay.classloader.WebClassloader;
import com.kay.mapper.Context;
import com.kay.mapper.Host;
import com.kay.mapper.Mapper;
import com.kay.mapper.Wrapper;
import com.kay.server.RequestProcessor;
import com.kay.servlet.HttpServlet;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.*;

/**
 * @Description:，入口函数
 * @Author: Kay
 * @CreateDate: 2021/8/3$ 9:44$
 * @UpdateUser: Kay
 * @UpdateDate: 2021/8/3$ 9:44$
 */
public class BootStrap {
    private int port = 8888;
    //    private Map<String, HttpServlet> servletMap = new HashMap<>();
    private Mapper mapper = new Mapper();


    public static void main(String[] args) {
        BootStrap bootStrap = new BootStrap();
        try {

            bootStrap.start();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }
    }

    /**
     * 1. 加载server.xml 获取host及端口信息
     * 2. 加载 webapps目录下项目，加载个项目对应的web.xml文件 封装Mapper对象
     * 3. 监听Socket监听 port 端口
     * 4. 开启线程池，处理HTTP请求 （将请求信息封装成Request对象，其中Request中包含 MapperData）
     */
    public void start() throws IOException, DocumentException {
        //  1.加载server.xml文件 读取对应的Host信息，存放到Mapper对象中
        loadServer();

        // 2. 加载webapps下的目录信息，读取每个目录根路径下的web.xml解析 servlet及url信息，结合项目信息（demo1 demo2）封装Mapper对象
        loadContext();

        // 定义一个线程池
        int corePoolSize = 10; // 线程池基本大小
        int maximumPoolSize = 50; //i 最大线程数
        long keepAliveTime = 100L; // 有效时间
        TimeUnit unit = TimeUnit.SECONDS; // 有效时间对应的单位
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50); // 队列
        ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 线程工厂
        // 线程拒绝策略 （当请求过多时，是丢弃旧的或者其他操作
        // This may occur when no more threads or queue slots are available because their bounds would be exceeded, or upon shutdown of the Executor
        RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();// 丢弃

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
                unit, workQueue, threadFactory, handler);

        // 3. 开启Socket端口绑定及监听
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println(" ServerSocket  port " + port);
        while (true) {
            Socket socket = serverSocket.accept();
            // todo
            RequestProcessor requestProcessor = new RequestProcessor(socket, mapper);
            // requestProcessor.start();
            threadPoolExecutor.execute(requestProcessor);
        }
    }

    /**
     * 加载 server.xml文件  读取对应的Host信息，存放到Mapper对象中
     */
    public void loadServer() throws DocumentException {
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("server.xml");
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element rootElement = document.getRootElement();
        //  List<Element> serviceElementList = rootElement.selectNodes("//Service");
        Element serviceElement = (Element) rootElement.selectSingleNode("Service"); // 当前案例，暂只考虑配置了一个Service的情况
        Element connectorElement = (Element) serviceElement.selectNodes("//Connector").get(0); // 暂只考虑一个Connector标签
        this.port = Integer.parseInt(connectorElement.attributeValue("port"));
        //  <Host name="localhost" appBase="D:/project2021/study/Tomcat/webapps">  </Host>
        List<Element> hostElementList = serviceElement.selectNodes("//Host");
        List<Host> hostList = new ArrayList<>();
        for (Element element : hostElementList) {
            String hostName = element.attributeValue("name"); // localhost
            String appBase = element.attributeValue("appBase");// 绝对路径 D:/project2021/study/Tomcat/webapps
            Host host = new Host(appBase, hostName, null);
            hostList.add(host);
        }
        mapper.setHostList(hostList);
    }


    /**
     * 加载 webapps 下项目目录
     */
    public void loadContext() {
        List<Host> hostList = mapper.getHostList();
        for (Host host : hostList) {
            String appBase = host.getAppBase();// 绝对路径 D:/project2021/study/Tomcat/webapps
            File file = new File(appBase); // D:/project2021/study/Tomcat/webapps
            if (file.exists() && file.isDirectory()) {
                File[] files = file.listFiles();  // demo1 demo2
                List<Context> contextList = new ArrayList<>();
                for (File webFile : files) {
                    if (webFile.isDirectory()) {
                        String name = webFile.getName(); // demo1 / demo2
                        //demo1 再往下一层目录，解析web.xml文件，获取对应webFile下的 url 与 servlet 的映射关系
                        Map<String, HttpServlet> servletMap = initServlet(appBase, name);
                        // 根据获取到 url 与 servlet 的映射关系。结合当前web应用（文件目录作为区分） 封装Context及Wrapper对象
                        Set<Map.Entry<String, HttpServlet>> entries = servletMap.entrySet();
                        List<Wrapper> wrapperList = new ArrayList<>();
                        for (Map.Entry<String, HttpServlet> entry : entries) {
                            Wrapper wrapper = new Wrapper(entry.getKey(), entry.getValue());
                            wrapperList.add(wrapper);
                        }
                        Context context = new Context(name, wrapperList);
                        contextList.add(context);
                    }
                }
                host.setObject(contextList);
            }
        }
        mapper.setHostList(hostList);
    }

    /**
     * 初始化 Servlet 信息
     * 解析 URL 与 HttpServlet之间的映射关系 servletMap
     */
    public Map<String, HttpServlet> initServlet(String appBase, String webName) {
        String path = appBase + "/" + webName + "/web.xml";
        System.out.println(" =====================>>>> web.xml path " + path);
        Map<String, HttpServlet> servletMap = new HashMap<>();
        File file = new File(path);
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(new FileInputStream(file));
            Element rootElement = document.getRootElement();
            List<Element> selectNodes = rootElement.selectNodes("//servlet");
            for (Element element : selectNodes) {
                Element servletNameElement = (Element) element.selectSingleNode("servlet-name"); //<servlet-name>demo</servlet-name>
                Element servletClassElement = (Element) element.selectSingleNode("servlet-class"); //<servlet-class>com.kay.myServlet.DemoServlet</servlet-class>
                String servletName = servletNameElement.getStringValue(); // demo
                String servletClass = servletClassElement.getStringValue(); // com.kay.myServlet.DemoServlet

                // 根据 servletName 获取到对应的 url-pattern
                Element servletMappingElement = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
                  /*  <servlet-mapping>
                        <servlet-name>demo</servlet-name>
                        <url-pattern>/demo</url-pattern>
                        </servlet-mapping>
                  */
                Element urlPatternElement = (Element) servletMappingElement.selectSingleNode("url-pattern");
                String urlPattern = urlPatternElement.getStringValue(); // /demo
                // 添加 URL 与 HttpServlet 类之间的映射
                // servletMap.put(urlPattern, (HttpServlet) (Class.forName(servletClass).newInstance()));

                // 生成HttpServlet的实例对象
//                servletClass = servletClass.replaceAll("\\.", "\\/");
//                String classPath = appBase + "/" + webName + "/" + servletClass + ".class";
                WebClassloader webClassloader = new WebClassloader(appBase + "/" + webName);
                servletMap.put(urlPattern, (HttpServlet) webClassloader.loadClass(servletClass).newInstance());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return servletMap;
    }
}
